/*
 * RTSAI - Real-Time Strategy engine for AI research
 * Copyright(C) 2006 DIMAp-UFRN, licensed under LGPL
 *
 * Interface para a linguagem script Lua
 */


#include <iostream>
#include <vector>
#include <string>
#include "CppLua.h"

using std::cerr;
using std::endl;
using std::string;
using std::vector;


lua_State* initLua() {
    lua_State *L = lua_open();
    luaL_openlibs(L);
    luaopen_rtsai(L);
    /*
    if (luaL_loadfile(L, "scripts/rtsai.lua") || lua_pcall(L, 0, LUA_MULTRET, 0)) {
        cerr << "Erro de script:" << endl << lua_tostring(L,-1) << endl;
        exit(1);
    }
    */
    return L;
}


/*! Split a string into several ones with a token separator.
 *  Implementation based on http://gcc.gnu.org snapcode.
 */
void tokenize(vector<string> &container, string const &in, const char delimiter = '.')
{
    const string::size_type len = in.length();
          string::size_type i = 0;

    while ( i < len )
    {
        // eat leading whitespace
        i = in.find_first_not_of (delimiter, i);
        if (i == string::npos)
            return;   // nothing left but white space

        // find the end of the token
        string::size_type j = in.find_first_of (delimiter, i);

        // push token
        if (j == string::npos) {
            container.push_back (in.substr(i));
            return;
        } else
            container.push_back (in.substr(i, j-i));

        // set up for next loop
        i = j + 1;
    }
}


lua_State* Lua::L = initLua();


Lua::Lua() {
    L = lua_open();
    openlibs();
}


Lua::Lua(const char *filename) {
    L = lua_open();
    openlibs();
    runfile(filename);
}


/*! Close Lua state descriptor */
Lua::~Lua() {
    lua_close(L);
}


/** Reset the Lua machine creating a new singleton instance */
void Lua::reset()
{
    L = initLua();
}

/*! Saves the content of a file at Lua state descriptor */
void Lua::runfile (const char *filename) {
    if (luaL_loadfile(L, filename) || lua_pcall(L, 0, LUA_MULTRET, 0)) {
        cerr << "Erro de script:" << endl << lua_tostring(L,-1) << endl;
        exit(1);
    }
}


/*! Executes the content of Lua state descriptor */
int Lua::execute() {
    return lua_pcall(L, 0, LUA_MULTRET, 0);
}


/*! Redefines to act like a lua_State pointer */
inline Lua::operator lua_State*() {
    return L;
}


/*! Returns a global variable of lua script */
void* Lua::getVar(char *varname) {
    lua_getglobal(L, varname);
    return tolua_tousertype(L,1,0);
}


/*! Read a lua variable name and push it int the lua stack for retrival. The
 *  variable name may have '.' characters for accessing a field of a table.
 *  The return value is the number of elements pushed in the stack.
 */
int Lua::push_stack(const string& var)
{
    vector<string> fields;
    tokenize(fields, var);
    vector<string>::const_iterator it = fields.begin();
    lua_getglobal(L, (*it).c_str());
    while ((++it) != fields.end())
    {
        lua_pushstring(L, (*it).c_str());
        lua_gettable(L, -2);
    }
    return fields.size();
}


int Lua::push_stack_set(const string& var, int &index)
{
    vector<string> fields;
    tokenize(fields, var);

    if (fields.size() > 1)
    {
        lua_getglobal(L, fields[0].c_str());
        int c = 1;
        for ( ; c < fields.size()-1 ; ++c)
        {
            lua_getfield(L, -1, fields[c].c_str());
        }
        lua_pushstring(L, fields[c].c_str());
        index = -3;
    }
    else
    {
        lua_pushstring(L, var.c_str());
        index = LUA_GLOBALSINDEX;
    }
    return fields.size() - 1;
}


/*! Returns the value of an integer lua variable. The variable name may have '.'
 *  characters for accessing a field of a table.
 */
int Lua::get_integer(const string& var)
{
    int c = push_stack(var);
    int v = lua_tointeger(L, -1);
    // remove the elements from the stack for clean it up
    lua_pop(L, c);
    return v;
}


void Lua::set_integer(const std::string& var, int value)
{
    int index;
    int c = push_stack_set(var, index);
    lua_pushinteger(L, value);
    lua_settable(L, index);
    lua_pop(L, c);
}


/*! Returns the value of a string lua variable. The variable name may have '.'
 *  characters for accessing a field of a table.
 */
const char* Lua::get_string(const string& var)
{
    int c = push_stack(var);
    const char* v = lua_tostring(L, -1);
    // remove the elements from the stack for clean it up
    lua_pop(L, c);
    return v;
}


void Lua::set_string(const std::string& var, const char *value)
{
    int index;
    int c = push_stack_set(var, index);
    lua_pushstring(L, value);
    lua_settable(L, index);
    lua_pop(L, c);
}


/*! Call a LUA function from C++. The function name may have '.' characters for
 *  accessing a function defined inside a table.
 *
 *  @param func The name of the function to call.
 *  @param sig A format string of input and output options with a ">" between them.
 *
 *  For example:
 *   callFunction("f", "dd>d", x, y, &z);
 *  Calls a function with two "double" arguments, returning a double.
 *
 *  @seealso call_function_error
 *  @note Method based on luainstance (http://www.steve.org.uk/Software/lua-c++/)
 */
void Lua::call_function(const string& func, const char *sig, ...) //
{
    va_list vl;
    int narg, nres;  /* number of arguments and results */

    va_start(vl, sig);
    int c = push_stack(func);
    // lua_getglobal( L, func );  /* get function */

    /* push arguments */
    narg = 0;
    while (*sig)
    {
        /* push arguments */
        switch (*sig++)
	{

          case 'd':  /* double argument */
            lua_pushnumber( L, va_arg(vl, double));
            break;

          case 'i':  /* int argument */
            lua_pushnumber( L, va_arg(vl, int));
            break;

          case 's':  /* string argument */
            lua_pushstring( L, va_arg(vl, char *));
            break;

          case '>':
            goto endwhile;

          default:
            call_function_error("invalid option (%c)\n", *(sig - 1));
        }
        narg++;
        luaL_checkstack( L, 1, "too many arguments");
    }
    endwhile:


    /* do the call */
    nres = strlen(sig);  /* number of expected results */
    if (lua_pcall( L, narg, nres, 0) != 0)  /* do the call */
      call_function_error("error running function `%s': %s\n", func.c_str(), lua_tostring( L, -1));

    /* retrieve results */
    nres = -nres;  /* stack index of first result */
    while (*sig)
    {
        /* get results */
        switch (*sig++)
	{

	  case 'd':  /* double result */
            if (!lua_isnumber( L, nres))
              call_function_error( "wrong result type\n");
            *va_arg(vl, double *) = lua_tonumber(L, nres);
            break;

	  case 'i':  /* int result */
	    if (!lua_isnumber( L, nres))
              call_function_error("wrong result type\n");
            *va_arg(vl, int *) = (int)lua_tonumber(L, nres);
            break;

          case 's':  /* string result */
            if (!lua_isstring( L, nres))
              call_function_error("wrong result type\n");
            *va_arg(vl, const char **) = lua_tostring(L, nres);
            break;

          default:
            call_function_error("invalid option (%c)\n", *(sig - 1));
        }
        nres++;
    }
    va_end(vl);
    lua_pop(L, c-1);
}


void Lua::call_function_error( const char *fmt, ...)
{
    va_list argp;
    va_start(argp, fmt);
    vfprintf(stderr,fmt, argp);
    va_end(argp);
    lua_close(L);
    exit(EXIT_FAILURE);
}
